home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Mac Magazin/MacEasy 14
/
Mac Magazin and MacEasy Magazine CD - Issue 14.iso
/
Wissenschaft & Technik
/
Caveman Sound
/
CMSoundSystem.c
< prev
next >
Wrap
Text File
|
1995-09-11
|
40KB
|
1,317 lines
/*****************************************************************************
* FILE: CMSoundSystem.c
* AUTHOR: David Hay
* CREATED: March 9, 1995
* DESCRIPTION: Routines for playing sound and music.
*
* Copyright © 1995 David Hay
*
* Permission to use, copy, and distribute this software and its documentation
* for any purpose is hereby granted without fee, provided that (i) the above
* copyright notices and this permission notice appear in all copies of the
* software and related documentation, and (ii) the names of David Hay and
* Caveman Creations may not be used in any advertising or publicity relating
* to the software without the specific, prior written permission of David Hay
*
* THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, EXPRESS,
* IMPLIED OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, ANY WARRANTY OF
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL
* DAVID HAY OR CAVEMAN CREATIONS BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
* RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF THE
* POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*****************************************************************************/
#ifndef __GESTALT__
#include <Gestalt.h>
#endif
#ifndef __SOUND__
#include <Sound.h>
#endif
#ifndef __TRAPS__
#include <Traps.h>
#endif
#ifndef __CMSOUNDSYSTEM__
#include "CMSoundSystem.h"
#endif
#ifndef PUBLIC
#define PUBLIC
#endif
#ifndef PRIVATE
#define PRIVATE static
#endif
#define kSoundDone 99L /* a sound is done playing */
#define kMusicDone 100L /* The music block is done playing */
#define kSoundResourceDone 101L /* a sound resource is done playing */
/*****************************************************************************
** ExtSndChannel -- Describes an extended sound channel.
**
** channel - the Sound Manager channel data. NOTE: this field
** MUST be first! This makes it possible for me to
** simply cast the extended sound channel to a
** sound channel for passing to Sound Manager routines.
**
** soundPlaying - The reference number of the sound currently playing
** on this sound channel. If no sound is playing, this
** is set to the value kNoSound. If a sound that is not
** registered with the sound system is playing on the
** sound channel, this has the value kSoundResource.
**
** isValid - Is the sound channel valid? If not a call to the
** routine SndNewChannel() is needed to make the
** channel valid again. Otherwise, the channel has
** already been allocated and sound may be played on it.
**
** soundData - if the sound playing is an unregistered sound
** resource, this is the sound resource playing on
** the sound channel.
**/
struct ExtSndChannel
{
SndChannel channel;
short soundPlaying;
Boolean isValid;
SndListHandle soundData;
};
typedef struct ExtSndChannel ExtSndChannel;
typedef ExtSndChannel *ExtSndChannelPtr;
/*****************************************************************************
** SoundSystem -- Describes private data the sound system
**
** numChannels - the number of sound channels open
** soundChannel - The list of available channels.
** numSounds - the number of sound that have been registered with
** the sound system.
** soundList - A list of sound handles that have been registered.
** soundData - Every time a sound handle is registered with the
** sound system, the sound header is found and placed
** in the corresponding slot in this list. This allows
** the sound system to play the sounds more efficently
** with bufferCmd's rather than using PlaySound().
** musicSequence - describes which sounds to play and in what order
** for a music sequence
** musicPosition - the current position in the music sequence
**
** smVersion - the version of the sound manager used.
** smHasStereo - is stereo sound is available?
** smHasStereoMixing
** - can the stereo be mixed into a single sound channel?
** smHasDoubleBuffer
** - Are double buffering routines available?
** smHasMultiChannels
** - Are we allowed to have more than one channel open
** at a time?
**/
struct SoundSystem
{
short numChannels;
ExtSndChannelPtr soundChannel[kMaxChannels];
short numSounds;
SndListHandle soundList[kMaxSounds];
SoundHeaderPtr soundData[kMaxSounds];
PlaySequencePtr musicSequence;
short musicPosition;
NumVersion smVersion;
Boolean smHasStereo :1;
Boolean smHasStereoMixing :1;
Boolean smHasDoubleBuffer :1;
Boolean smHasMultiChannels :1;
};
typedef struct SoundSystem SoundSystem;
/************************ PRIVATE FUNCTION PROTOTYPES ************************/
PRIVATE OSErr PlayMusicBlock( short channelNum );
PRIVATE pascal void SoundCallBack( SndChannelPtr theChannel, SndCommand theCmd );
PRIVATE pascal void MusicCallBack( SndChannelPtr theChannel, SndCommand theCmd );
/************************** PRIVATE GLOBAL VARIABLES *************************/
PRIVATE SndCallBackUPP soundCallBackUPP; /* callback when sounds are done */
PRIVATE SoundSystem snd; /* sound system state information */
/*===========================================================================*/
/* PUBLIC FUNCTION DEFINITIONS */
/*===========================================================================*/
/*----------------------- INITIALIZATION/DEALLOCATION -----------------------*/
/*****************************************************************************
* FUNCTION: CMSInitSound
*
* INPUT: short numChannels -- Number of sound channels to allocate
* RETURNS: OSErr -- Returns any errors the occur.
*
* DESCRIPTION: Initializes the sound system for use. It creates the requested
* number of channels and initializes internal data. Any error
* conditions are returned.
*****************************************************************************/
PUBLIC OSErr CMSInitSound( short numChannels )
{
ExtSndChannelPtr aChannel; /* A channel to initialize */
short ii; /* misc counter */
long feature; /* result value from Gestalt() */
OSErr err;
/* Get some information about the current sound manager. First we
** find out what version of the sound manager is available. To do
** this we first have to check if the _SoundDispatch trap is
** available. If so, then we can call SndSoundManagerVersion() to
** get the version number. Otherwise, the enhanced sound manager
** is not present so set the version number to 1.0
**/
if ( GetToolTrapAddress(_Unimplemented) != GetToolTrapAddress(_SoundDispatch) )
{
snd.smVersion = SndSoundManagerVersion();
}
else
{
snd.smVersion.majorRev = 1;
snd.smVersion.minorAndBugRev = 0;
snd.smVersion.stage = finalStage;
snd.smVersion.nonRelRev = 0;
}
/* Now that we have the version number, check for some capabilities
** on the current system using gestalt. To check if double buffering
** or multiple channels are available, we check two ways. If Sound
** Manager 3.0 is available, we can simply look at the result
** returned from Gestalt. Otherwise, we see if the ASC chip is
** present to determine if the desired features are available.
**/
err = Gestalt( gestaltSoundAttr, &feature );
if ( err == noErr )
{
snd.smHasStereo = ((feature & (1L << gestaltStereoCapability)) != 0);
snd.smHasStereoMixing = ((feature & (1L << gestaltStereoMixing)) != 0);
if ( snd.smVersion.majorRev >= 3 )
{
snd.smHasDoubleBuffer =
((feature & (1L << gestaltSndPlayDoubleBuffer)) != 0);
snd.smHasMultiChannels =
((feature & (1L << gestaltMultiChannels)) != 0);
}
else
{
err = Gestalt( gestaltHardwareAttr, &feature );
if ( err == noErr )
{
snd.smHasDoubleBuffer =
((feature & (1L << gestaltHasASC)) != 0);
snd.smHasMultiChannels = snd.smHasDoubleBuffer;
}
}
}
/* Initialize the sound channel information so we know
** what to dispose of if an error occurs.
**/
snd.numChannels = 0;
for ( ii = 0; ii < kMaxChannels; ii++ )
snd.soundChannel[ii] = NULL;
/* Initialize the sound data information to indicate that
** no sounds have been registered with the sound system
**/
snd.numSounds = 0;
for ( ii = 0; ii < kMaxSounds; ii++ )
{
snd.soundData[ii] = NULL;
snd.soundList[ii] = NULL;
}
/* Initialize the music information to indicate that no
** music has been loaded
**/
snd.musicPosition = kNoSound;
snd.musicSequence = NULL;
/* Setup the callback routine pointer for sounds. We will use the
** same routine to handle sounds and music by putting a different
** number in the callback command to determine what to do.
**/
soundCallBackUPP = NewSndCallBackProc( SoundCallBack );
/* Allocate the sound channels. If more than one sound channel is
** requested and multiple sound channels are not available, then
**
**/
for ( ii = 0; ii < numChannels; ii++ )
{
aChannel = (ExtSndChannelPtr) NewPtr( sizeof( ExtSndChannel ) );
if ( !aChannel )
{
err = MemError();
break;
}
else
{
aChannel->channel.qLength = stdQLength;
aChannel->channel.userInfo = ii;
aChannel->soundPlaying = kNoSound;
aChannel->isValid = false;
aChannel->soundData = NULL;
snd.soundChannel[ii] = aChannel;
}
}
if ( err == noErr )
{
snd.numChannels = numChannels;
}
else
{
CMSDisposeSound();
}
return err;
}
/*****************************************************************************
* FUNCTION: CMSDisposeSound
*
* INPUT: None.
* RETURNS: Nothing.
*
* DESCRIPTION: Stops all sounds and music and frees any memory allocated.
*****************************************************************************/
PUBLIC void CMSDisposeSound( void )
{
OSErr err;
short ii;
/* Destroy all of the sound channels
**/
for ( ii = 0; ii < snd.numChannels; ii++ )
{
if ( snd.soundChannel[ii] )
{
CMSCloseChannel( ii );
DisposePtr( (Ptr) snd.soundChannel[ii] );
snd.soundChannel[ii] = NULL;
}
}
DisposeRoutineDescriptor( soundCallBackUPP );
/* Dispose of the registered sound data
**/
for ( ii = 0; ii < snd.numSounds; ii++ )
{
if ( snd.soundList[ii] )
{
HUnlock( (Handle) snd.soundList[ii] );
DisposeHandle( (Handle) snd.soundList[ii] );
}
snd.soundData[ii] = NULL;
snd.soundList[ii] = NULL;
}
/* Dispose of the music (if any)
**/
if ( snd.musicSequence )
{
DisposePtr( (Ptr) snd.musicSequence );
snd.musicSequence = NULL;
}
}
/*---------------------------- CHANNEL MANAGEMENT ---------------------------*/
/*****************************************************************************
* FUNCTION: CMSCloseChannel
*
* INPUT: short channelNum -- the sound channel to close.
* RETURNS: OSErr -- result code.
*
* DESCRIPTION: Stops the sound on the given channel and free's the channel.
* Sound channels should be closed when an application receives
* a suspend event. If the channel is already closed, nothing
* happens and noErr is returned.
*****************************************************************************/
PUBLIC OSErr CMSCloseChannel( short channelNum )
{
OSErr err;
err = noErr;
if ( snd.soundChannel[channelNum]->isValid )
{
err = SndDisposeChannel( (SndChannelPtr) snd.soundChannel[channelNum],
true );
snd.soundChannel[channelNum]->isValid = false;
}
return err;
}
/*****************************************************************************
* FUNCTION: CMSCloseAllChannels
*
* INPUT: None.
* RETURNS: OSErr -- result code.
*
* DESCRIPTION: Closes all of the currently open channels.
*****************************************************************************/
PUBLIC OSErr CMSCloseAllChannels( void )
{
short ii;
OSErr err;
err = noErr;
for ( ii = 0; ii < snd.numChannels; ii++ )
err = CMSCloseChannel( ii );
return err;
}
/*****************************************************************************
* FUNCTION: CMSOpenChannel
*
* INPUT: short channelNum -- the channel to open.
* RETURNS: OSErr -- result code.
*
* DESCRIPTION: Opens the channel identified by the given channel number. If
* the channel is already open, nothing is done. Sound may not be
* played on a channel until it has been opened.
*****************************************************************************/
PUBLIC OSErr CMSOpenChannel( short channelNum )
{
OSErr err;
err = noErr;
if ( !snd.soundChannel[channelNum]->isValid )
{
err = SndNewChannel( (SndChannelPtr*) &snd.soundChannel[channelNum],
sampledSynth, initNoInterp + initMono,
soundCallBackUPP );
if ( err == noErr )
snd.soundChannel[channelNum]->isValid = true;
}
return err;
}
/*****************************************************************************
* FUNCTION: CMSOpenAllChannels
*
* INPUT: None.
* RETURNS: OSErr -- result code
*
* DESCRIPTION: Opens all of the channels that have been allocated.
*****************************************************************************/
PUBLIC OSErr CMSOpenAllChannels( void )
{
short ii;
OSErr err;
err = noErr;
for ( ii = 0; ii < snd.numChannels; ii++ )
err = CMSOpenChannel( ii );
return err;
}
/*****************************************************************************
* FUNCTION: CMSStopSound
*
* INPUT: short channelNum -- the channel to stop sound on.
* RETURNS: OSErr -- result code.
*
* DESCRIPTION: Stops any playing sound on the given sound channel.
*****************************************************************************/
PUBLIC OSErr CMSStopSound( short channelNum )
{
SndCommand theCommand;
ExtSndChannelPtr sndChannel;
OSErr err;
sndChannel = snd.soundChannel[channelNum];
/* Flush the sound channel of any other sound commands
**/
theCommand.cmd = flushCmd;
theCommand.param1 = 0;
theCommand.param2 = 0L;
err = SndDoImmediate( (SndChannelPtr) sndChannel, &theCommand );
/* Send a quiet command to stop any currently playing sounds
**/
if ( err == noErr )
{
theCommand.cmd = quietCmd;
theCommand.param1 = 0;
theCommand.param2 = 0L;
err = SndDoImmediate( (SndChannelPtr) sndChannel, &theCommand );
}
sndChannel = snd.soundChannel[channelNum];
sndChannel->soundPlaying = kNoSound;
if ( sndChannel->soundData )
{
HUnlock( (Handle) sndChannel->soundData );
sndChannel->soundData = NULL;
}
return err;
}
/*****************************************************************************
* FUNCTION: CMSIdleSoundTask
*
* INPUT: None.
* RETURNS: Nothing.
*
* DESCRIPTION: Performs any idle tasks needed when a sound resource is being
* played. It checks each of the sound channels to determine if
* a sound is done playing and unlocks the sound resource if so.
* This allows sound resources to be purged after they are done
* playing.
*****************************************************************************/
PUBLIC void CMSIdleSoundTask( void )
{
short ii;
ExtSndChannelPtr sndChannel;
/* Loop over the available channels checking to see if one has
** fallen silent. If so and there is a sound resource attached
** to the channel, then unlock the sound so that it may be
** purged. Also detach the sound resource from the channel so
**/
for ( ii = 0; ii < snd.numChannels; ii++ )
{
sndChannel = snd.soundChannel[ii];
if ( sndChannel->soundPlaying == kNoSound && sndChannel->soundData )
{
HUnlock( (Handle) sndChannel->soundData );
sndChannel->soundData = NULL;
}
}
}
/*****************************************************************************
* FUNCTION: CMSGetSoundPlaying
*
* INPUT: short channelNum -- the channel to query.
* OUTPUT: short* refNum -- reference to the sound playing.
* RETURNS: OSErr -- result code.
*
* DESCRIPTION: Returns the sound playing on the given channel in refNum. If
* no sound is currently playing, kNoSound is returned as the
* reference number. Any error conditions are returned.
*****************************************************************************/
PUBLIC OSErr CMSGetSoundPlaying( short channelNum, short* refNum )
{
if ( channelNum >= 0 && channelNum < snd.numChannels )
{
*refNum = snd.soundChannel[channelNum]->soundPlaying;
return noErr;
}
return badChannel;
}
/*****************************************************************************
* FUNCTION: CMSWaitForSilence
*
* INPUT: short channelNum -- the channel to wait on
* RETURNS: OSErr -- result code.
*
* DESCRIPTION: Waits for the current sound to stop playing on the indicated
* sound channel. If no sounds are playing on the channel,
* the function returns immediately.
*****************************************************************************/
PUBLIC OSErr CMSWaitForSilence( short channelNum )
{
if ( channelNum >= 0 && channelNum < snd.numChannels )
{
/* We turn off global optimizer to keep the compiler from putting
** putting our loop control variable into a register. Since we
** are waiting for an interrupt to occur, putting the LCV in a
** register would put us in an infinite loop. By disabling the
** compiler's ability to assign variables to registers, we are
** able to avoid this nasty problem. I don't know if this hack
** will work on the PPC since I don't have a PPC compiler.
**/
#pragma options( !global_optimizer )
while( snd.soundChannel[channelNum]->soundPlaying != kNoSound )
{} /* Do nothing, just wait for the sound to complete */
return noErr;
}
return badChannel; /* invalid channel specified */
}
/*-------------------------- SOUND DATA MANAGEMENT --------------------------*/
/*****************************************************************************
* FUNCTION: CMSRegisterSound
*
* INPUT: SndListHandle theSound -- the sound to register
* short* refNum -- sound reference for later playback
* RETURNS: OSErr -- error code.
*
* DESCRIPTION: Registers the given sound handle with the sound system. A
* reference to the sound is returned in refNum which can be
* used to play the sound at a later time.
*****************************************************************************/
PUBLIC OSErr CMSRegisterSound( SndListHandle sndHandle, short* refNum )
{
short ii;
SoundHeaderPtr sndHeader;
OSErr err;
err = noErr;
*refNum = kNoSound;
if ( sndHandle != NULL )
{
/* First we get the sound header from the sound handle so
** that the sound manager does not have to parse the sound
** resource each time the sound is played.
**/
sndHeader = CMSGetSoundHeader( sndHandle );
/* Now we need to find a free slot in the list of available
** sounds. Once we find a slot, insert the sound data into
** that slot and return the slot number as the sound
** reference.
**/
for ( ii = 0; ii < snd.numSounds; ii++ )
{
if ( snd.soundData[ii] == NULL )
{
*refNum = ii;
snd.soundData[ii] = sndHeader;
snd.soundList[ii] = sndHandle;
break;
}
}
/* There aren't enough slots, so bump the number of slots
** if possible and insert the sound at the end of the sound
** list. If there simply isn't any more room, return an error.
**/
if ( *refNum == kNoSound && snd.numSounds < kMaxSounds )
{
*refNum = snd.numSounds;
snd.soundData[snd.numSounds++] = sndHeader;
}
else
{
err = notEnoughBufferSpace;
}
}
return err;
}
/*****************************************************************************
* FUNCTION: CMSRemoveSound
*
* INPUT: short refNum -- reference to the sound to remove
* RETURNS: OSErr -- error code.
*
* DESCRIPTION: Removes the referenced sound from the sound system. If the
* sound is currently playing on a sound channel, it is stopped
* before the sound is disposed of.
*****************************************************************************/
PUBLIC OSErr CMSRemoveSound( short refNum )
{
short ii;
SndCommand theCommand;
OSErr err;
err = noErr;
if ( refNum >= 0 && refNum < snd.numSounds )
{
/* Stop the sound if it is playing on one
** of the sound channels
**/
for ( ii = 0; ii < snd.numChannels; ii++ )
{
if ( snd.soundChannel[ii]->soundPlaying == refNum )
{
err = CMSStopSound( ii );
}
if ( err != noErr ) break;
}
/* Dispose of the sound handle and mark it's slot in the
** list of registered sounds as now available.
**/
if ( err == noErr )
{
HUnlock( (Handle) snd.soundList[refNum] );
DisposeHandle( (Handle) snd.soundList[refNum] );
snd.soundData[ii] = NULL;
snd.soundList[ii] = NULL;
}
}
return err;
}
/*****************************************************************************
* FUNCTION: CMSLoadSound
*
* INPUT: short soundID -- resource ID of the sound to load.
* short* refNum -- sound reference for later playback
* RETURNS: OSErr -- error code.
*
* DESCRIPTION: Loads the indicated sound from the current resource file. A
* reference to the sound is returned in refNum which can be used
* to play the sound back at a later time.
*****************************************************************************/
PUBLIC OSErr CMSLoadSound( short soundID, short* refNum )
{
Handle sndHandle;
OSErr err;
err = noErr;
/* Get the sound resource and detach it from the resource file.
**/
sndHandle = GetResource( 'snd ', soundID );
if ( sndHandle == NULL )
err = ResError();
if ( err == noErr )
{
DetachResource( (Handle) sndHandle );
err = ResError();
}
/* Now that we have the sound handle, register the sound
** with the sound system.
**/
if ( err == noErr )
err = CMSRegisterSound( (SndListHandle) sndHandle, refNum );
return err;
}
/*****************************************************************************
* FUNCTION: CMSPlaySound
*
* INPUT: short refNum -- reference to the sound to play.
* short channelNum -- the channel to play the sound on.
* RETURNS: OSErr -- error code.
*
* DESCRIPTION: Plays the sound refered to by the given reference number on
* the indicated sound channel. If the sound channel is not
* already open, it is opened.
*****************************************************************************/
PUBLIC OSErr CMSPlaySound( short refNum, short channelNum )
{
SndCommand theCommand;
ExtSndChannelPtr theChannel;
OSErr err;
err = noErr;
if ( channelNum >= 0 && channelNum < snd.numChannels &&
refNum >= 0 && refNum < snd.numSounds )
{
/* Open the sound channel if it is not already. Otherwise, stop
** any sound that may be playing on that sound channel.
**/
if ( !snd.soundChannel[channelNum]->isValid )
{
err = CMSOpenChannel( channelNum );
}
else
{
err = CMSStopSound( channelNum );
}
theChannel = snd.soundChannel[channelNum];
/* Play the sound and install a completion callback routine
**/
if ( err == noErr )
{
theCommand.cmd = bufferCmd;
theCommand.param1 = 0;
theCommand.param2 = (long) snd.soundData[refNum];
err = SndDoImmediate( (SndChannelPtr) theChannel, &theCommand );
}
if ( err == noErr )
{
theCommand.cmd = callBackCmd;
theCommand.param1 = kSoundDone;
theCommand.param2 = SetCurrentA5();
err = SndDoCommand( (SndChannelPtr) theChannel, &theCommand, true );
}
if ( err == noErr )
theChannel->soundPlaying = refNum;
}
return err;
}
/*****************************************************************************
* FUNCTION: CMSPlaySoundResource
*
* INPUT: short resID -- resource ID of the sound to play.
* short channelNum -- channel to play the sound on.
* Boolean async -- play the sound asychronously?
* RETURNS: OSErr -- result code.
*
* DESCRIPTION: Loads the resource indicated and plays it on the appropriate
* channel. Since the sound handle cannot be freed at callback
* time. The routine CMSWaitForQuiet() should be used to wait
* for the sound channel to become silent before calling
* CMSStopSound() to free the sound resource.
*****************************************************************************/
PUBLIC OSErr CMSPlaySoundResource( short resID, short channelNum, Boolean async )
{
Handle sndHandle;
SndCommand theCommand;
OSErr err;
err = noErr;
if ( channelNum >= 0 && channelNum < snd.numChannels )
{
/* Open the sound channel if it is not already. Otherwise, stop
** any sound that may be playing on that sound channel.
**/
if ( !snd.soundChannel[channelNum]->isValid )
{
err = CMSOpenChannel( channelNum );
}
else
{
err = CMSStopSound( channelNum );
}
/* Get the sound resource. Then lock it down and make it purgeable
** so that it can be freed after the sound is finished playing.
**/
sndHandle = GetResource( 'snd ', resID );
if ( sndHandle == NULL )
return ResError();
HLockHi( sndHandle );
HPurge( sndHandle );
/* Play the sound resource and install a callback routine to
** handle the completion of the sound
**/
err = SndPlay( (SndChannelPtr) snd.soundChannel[channelNum],
(SndListHandle) sndHandle, async );
if ( err == noErr )
{
theCommand.cmd = callBackCmd;
theCommand.param1 = kSoundResourceDone;
theCommand.param2 = SetCurrentA5();
err = SndDoCommand( (SndChannelPtr) snd.soundChannel[channelNum],
&theCommand, true );
snd.soundChannel[channelNum]->soundData = (SndListHandle) sndHandle;
snd.soundChannel[channelNum]->soundPlaying = kSoundResource;
}
}
return err;
}
/*----------------------------- MUSIC MANAGEMENT ----------------------------*/
/*****************************************************************************
* FUNCTION: CMSLoadMusic
*
* INPUT: short musicID -- ID of the music sequence resource
* RETURNS: OSErr -- result code.
*
* DESCRIPTION: Loads the music described by a MUSL resource. It loads each
* of the sounds listed in the play sequence and registers them
* with the sound system. It also sets up the internal play
* sequence so that the music can later be played with a call
* to CMSPlayMusic().
*****************************************************************************/
PUBLIC OSErr CMSLoadMusic( short musicID )
{
PlaySequenceHandle sequenceH;
PlaySequencePtr sequencePtr;
Size sequenceSize;
short ii, jj;
short soundLoaded[kMaxSounds];
short soundRef;
short firstSound;
short lastSound;
OSErr err;
/* First we get the music list resource and copy it into
** a newly allocated block of memory
**/
sequenceH = (PlaySequenceHandle) GetResource( kMusicResType, musicID );
if ( !sequenceH ) return ResError();
sequenceSize = GetHandleSize( (Handle) sequenceH );
err = MemError();
if ( err == noErr )
{
sequencePtr = (PlaySequencePtr) NewPtr( sequenceSize );
if ( sequencePtr == NULL )
err = MemError();
}
if ( err == noErr && sequencePtr )
{
HLock( (Handle) sequenceH );
BlockMove( (Ptr)(*sequenceH),(Ptr)(sequencePtr), sequenceSize );
HUnlock( (Handle) sequenceH );
}
ReleaseResource( (Handle) sequenceH );
if ( err == noErr )
{
/* Figure out the range of sound resources we are dealing with so
** that we know which sounds to load in. It is assumed that the
** sounds in the music sequence are contiguous and no other sounds
** are involved.
**/
firstSound = sequencePtr->sequence[0];
lastSound = sequencePtr->sequence[0];
for ( ii = 1; ii < sequencePtr->sequenceLength; ii++ )
{
if ( sequencePtr->sequence[ii] < firstSound )
firstSound = sequencePtr->sequence[ii];
if ( sequencePtr->sequence[ii] > lastSound )
lastSound = sequencePtr->sequence[ii];
}
/* Load the sound data for the music. The sequence resource lists
** the 'snd ' resources to play, so load each one in and update
** the music sequence to refer to the new sound, rather than the
** resource number.
**/
for ( ii = firstSound; ii <= lastSound; ii++ )
{
err = CMSLoadSound( ii, &soundRef );
if ( err != noErr ) break;
soundLoaded[ii - firstSound] = soundRef;
for ( jj = 0; jj < sequencePtr->sequenceLength; jj++ )
{
if ( sequencePtr->sequence[jj] == ii )
sequencePtr->sequence[jj] = soundRef;
}
}
}
/* Cleanup if there were any errors
**/
if ( err == noErr )
{
sequencePtr->loopStart--;
snd.musicSequence = sequencePtr;
}
else if ( sequencePtr )
{
/* Dispose of the sounds we could load before the error occured.
**/
ii -= firstSound;
while ( --ii >= 0 )
CMSRemoveSound( soundLoaded[ii] );
DisposePtr( (Ptr) sequencePtr );
snd.musicSequence = NULL;
}
return err;
}
/*****************************************************************************
* FUNCTION: CMSRemoveMusic
*
* INPUT: None.
* RETURNS: OSErr -- result code.
*
* DESCRIPTION: Removes a previously loaded piece of music from the sound
* system. All sounds associated with the music are removed as
* is the play sequence.
*****************************************************************************/
PUBLIC OSErr CMSRemoveMusic( void )
{
PlaySequencePtr sequencePtr;
short ii;
OSErr err;
err = noErr;
sequencePtr = snd.musicSequence;
if ( sequencePtr )
{
/* Loop through the entire play sequence and release each
** sound. If the sound has already been released, skip it
** and move on to the next one.
**/
for ( ii = 0; ii < sequencePtr->sequenceLength; ii++ )
{
if ( snd.soundData[sequencePtr->sequence[ii]] )
CMSRemoveSound( sequencePtr->sequence[ii] );
}
DisposePtr( (Ptr) sequencePtr );
snd.musicSequence = NULL;
snd.musicPosition = 0;
}
return err;
}
/*****************************************************************************
* FUNCTION: CMSPlayMusic
*
* INPUT: short channelNum -- the sound channel to play music on.
* RETURNS: OSErr -- result code.
*
* DESCRIPTION: Plays the music previously loaded by CMSLoadMusic. The music
* is played on the referenced sound channel.
*****************************************************************************/
PUBLIC OSErr CMSPlayMusic( short channelNum )
{
short firstSound;
short ii;
OSErr err;
err = noErr;
if ( snd.musicSequence != NULL )
{
/* If the sound channel is not already open, then open it.
** Otherwise, stop any sound that was previously playing on it.
**/
if ( !snd.soundChannel[channelNum]->isValid )
err = CMSOpenChannel( channelNum );
else
err = CMSStopSound( channelNum );
/* Reset the music position and then play the first
** block of music.
**/
if ( err == noErr )
{
snd.musicPosition = 0;
err = PlayMusicBlock( channelNum );
}
}
return err;
}
/*----------------------------- UTILITY ROUTINES ----------------------------*/
/*****************************************************************************
* FUNCTION: CMSGetSoundHeader
*
* INPUT: SndListHandle sndHandle -- the sound handle to extract from
* RETURNS: SoundHeaderPtr -- the header in the sound resource.
*
* DESCRIPTION: Obtains a pointer to the sound header in the given sound
* resource. Because it returns a pointer into the handle, the
* handle is locked and should not be unlocked until the sound
* header is no longer needed. If there is not a sound header
* in the given resource, then NULL is returned. Otherwise a
* valid sound header is returned. It returns a pointer to a
* sampled sound header even if the sound header is actually an
* extended sound header or a compressed sound header.
*****************************************************************************/
PUBLIC SoundHeaderPtr CMSGetSoundHeader( SndListHandle sndHandle )
{
long offset; /* offset to sound header */
OSErr err;
HLockHi( (Handle) sndHandle );
/* compute offset to sound header and use that offset to
** return a pointer into the sound handle.
**/
err = CMSGetSoundHeaderOffset( sndHandle, &offset );
if ( err != noErr ) /* no sound header in resource */
return NULL;
else /* compute address of sound header */
return ((SoundHeaderPtr)((Ptr)(*sndHandle) + offset));
}
/*****************************************************************************
* FUNCTION: CMSGetSoundHeaderOffset
*
* INPUT: SndListHandle sndHandle -- the sound handle to extract from
* OUTPUT: long* theOffset -- offset to the sound header.
* RETURNS: OSErr -- result code.
*
* DESCRIPTION: Traverses a sound resource until it reaches the sound data.
* It returns, in the offset parameter, the offset in bytes from
* the beginning of a sound resource to the sound header.
*****************************************************************************/
PUBLIC OSErr CMSGetSoundHeaderOffset( SndListHandle soundHandle,
long* theOffset )
{
Ptr sndPtr; /* to navigate resource */
long offset; /* offset into resource */
short numSynths; /* info about resource */
short numCmds; /* info about resource */
Boolean isDone; /* are we done yet? */
OSErr err;
/* If we have Sound Manager 3.0 or greater, then let it do
** the work for us. Otherwise, we have to parse the sound
** resource ourselves.
**/
if ( snd.smVersion.majorRev >= 3 )
return GetSoundHeaderOffset( soundHandle, theOffset );
/* Initialize variables.
**/
offset = 0L; /* return 0 if no sound header found */
sndPtr = (Ptr)*soundHandle; /* point to start of resource data */
isDone = false; /* haven't yet found sound header */
err = noErr;
/* Skip everything before sound commands.
**/
switch ( ((SndListPtr) sndPtr)->format )
{
case firstSoundFormat: /* format 1 'snd ' resource */
numSynths = ((SndListPtr) sndPtr)->numModifiers;
sndPtr += 2 * sizeof( short ) + numSynths * sizeof( ModRef );
break;
secondSoundFormat: /* format 2 'snd ' resource */
sndPtr += 2 * sizeof( short );
break;
default: /* unrecognized resource format */
err = badFormat;
isDone = true;
break;
}
/* Find number of commands and move to start of first command.
**/
numCmds = *(short*)sndPtr;
sndPtr += sizeof( short );
/* Search for bufferCmd or soundCmd to obtain sound header.
**/
while ( numCmds >= 1 && !isDone )
{
if ( (*(short*)sndPtr) == bufferCmd + dataOffsetFlag ||
(*(short*)sndPtr) == soundCmd + dataOffsetFlag )
{
/* bufferCmd or soundCmd found, copy offset
** from sound command
**/
offset = ((SndCommand*)sndPtr)->param2;
isDone = true; /* get out of loop */
}
else
{
/* soundCmd or bufferCmd not found move to next command
**/
sndPtr += sizeof( SndCommand );
--numCmds;
}
}
*theOffset = offset; /* return offset */
return err; /* return result code */
}
/*----------------------------- INQUIRY ROUTINES ----------------------------*/
/*****************************************************************************
* FUNCTION: CMSHasNewSoundManager
*
* INPUT: None
* RETURNS: Boolean -- is the new sound manager present?
*
* DESCRIPTION: Determines if the current sound manager is the new one and
* returns true if present.
*****************************************************************************/
PUBLIC Boolean CMSHasNewSoundManager( void )
{
NumVersion sndVersion;
if ( GetToolTrapAddress(_Unimplemented) != GetToolTrapAddress(_SoundDispatch) )
{
sndVersion = SndSoundManagerVersion();
return (sndVersion.majorRev >= 2);
}
return false;
}
/*****************************************************************************
* FUNCTION: CMSHasMultipleChannels
*
* INPUT: None
* RETURNS: Boolean -- Are more than one channel supported?
*
* DESCRIPTION: Determines if multiple channels are availble.
*****************************************************************************/
PUBLIC Boolean CMSHasMultipleChannels( void )
{
OSErr err;
long feature;
err = Gestalt( gestaltSoundAttr, &feature );
if ( err == noErr )
{
if ( CMSHasNewSoundManager() && SndSoundManagerVersion().majorRev >= 3 )
{
return ((feature & (1L << gestaltMultiChannels)) != 0);
}
else
{
err = Gestalt( gestaltHardwareAttr, &feature );
if ( err == noErr )
return ((feature & (1L << gestaltHasASC)) != 0);
}
}
return false;
}
/*===========================================================================*/
/* PRIVATE FUNCTION DEFINITIONS */
/*===========================================================================*/
/*****************************************************************************
* FUNCTION: PlayMusicBlock
*
* INPUT: short channelNum -- the channel to play the block on.
* RETURNS: OSErr -- result code.
*
* DESCRIPTION: Plays the next music block on the given channel.
*****************************************************************************/
PRIVATE OSErr PlayMusicBlock( short channelNum )
{
SndCommand theCommand;
short soundRef;
OSErr err;
if ( snd.musicPosition < 0 )
return noErr;
soundRef = snd.musicSequence->sequence[snd.musicPosition];
if ( soundRef < 0 )
return noErr;
theCommand.cmd = bufferCmd;
theCommand.param1 = 0;
theCommand.param2 = (long) snd.soundData[soundRef];
err = SndDoImmediate( (SndChannelPtr) snd.soundChannel[channelNum],
&theCommand );
theCommand.cmd = callBackCmd;
theCommand.param1 = kMusicDone;
theCommand.param2 = SetCurrentA5();
err = SndDoCommand( (SndChannelPtr) snd.soundChannel[channelNum],
&theCommand, false );
snd.soundChannel[channelNum]->soundPlaying = soundRef;
return err;
}
/*****************************************************************************
* FUNCTION: SoundCallBack
*
* INPUT: SndChannelPtr theChannel -- the current sound channel.
* SndCommand theCmd -- the command that caused this.
* RETURNS: Nothing.
*
* DESCRIPTION: This is the sound callback for the sound system. It has
* different behaivor depending on the contents of the command.
*****************************************************************************/
PRIVATE pascal void SoundCallBack( SndChannelPtr theChannel, SndCommand theCmd )
{
long myA5;
short channelNum;
myA5 = SetA5( theCmd.param2 );
switch( theCmd.param1 )
{
case kSoundDone:
case kSoundResourceDone:
/* A sound has finished playing on the sound channel so
** mark the sound channel as no longer playing any sound
** and return.
**/
channelNum = theChannel->userInfo;
snd.soundChannel[channelNum]->soundPlaying = kNoSound;
break;
case kMusicDone:
/* Advance the music position counter. If we have reached
** the end of the music, loop back to the appropriate place
** in the sequence.
**/
if ( (++snd.musicPosition) >= snd.musicSequence->sequenceLength )
snd.musicPosition = snd.musicSequence->loopStart;
/* Extract the channel number and play the next music
** block on that channel.
**/
channelNum = theChannel->userInfo;
PlayMusicBlock( channelNum );
break;
}
myA5 = SetA5( myA5 );
}